unit u_terminal;

{
    Esta lib foi criada em 03/04/2015 e gerencia somente uma conexão por vez nos WorkBench
    Para multiplas conexões podemos explorar os recuros do tList
}

interface

{$IFDEF VER280} // XE7
uses Classes, SysUtils, DateUtils, u_modbus, u_uart, Vcl.Forms;
{$ELSE}
uses Classes, SysUtils, DateUtils, u_modbus, u_uart;
{$ENDIF}

const
    tempCELSIUS		= 0;
    tempFAHRENHEIT	= 1;
    tempKELVIN 		= 2;

    maxNUMBER 			= 9999;	// numero máximo exibido no display
    maxTEMP_WORK 		= 70;	// Temperatura máxima de trabalho do worktemp 70ºC >>> temp é multiplicada por 10
    minTEMP_WORK 		= -30; 	// Temperatura minima de trabalho do worktemp -30ºC >>> temp é multiplicada por 10

    nTHERMOMETER        = 1;    // Quantidade de termômetro que o RH deve gerenciar. O ID modbus do termômetro deve ser sequencial de 1 a quantidade de termômetro
    EXP_nSLOTS_SAMPES   = 600;  // 600 = 10 minutos. Quantos slots guardaremos para as amostras das temperaturas
    nBUFFER_SAMPLES_READ = 50;  // Quantidade de registradores dos resultado vamos ler por vez. Máximo 120

    // status dos sensores de temperatura
    stsTEMP_READING	= 0;
    stsTEMP_OK	    = 1;
    stsTEMP_ERRMIN  = 2;
    stsTEMP_ERRMAX  = 3;

type
    // comandos para serem enviados ao WorkTemp
    tCommand = (
        cmdNONE,
        cmdGET_INFO,        // Comando para ler os registradores: modelo, versão firmware e modo de operações dos reles
        cmdGET_ACTION,      // Comando para ler que ação o recurso de hardware está fazendo agora
                                // 0: O recurso de hardware não está fazendo nada;
                                // 1: O recurso de hardware está fazendo um experimento ou uma configuração nos termômetros.
        cmdGET_STATUS,      // Comando para o status do experimento
                                // 0: Sinaliza que o experimento ou processo de configuração dos termômetros foi feito com sucesso;
                                // 1: Não foi possível se comunicar pelo menos com um termômetro;
                                // 2: Pelo menos um dos termômetros deu erro de leitura de temperatura, ou a mesma ficou fora dos limites de trabalho
        cmdGET_SAMPLES,     // Comando para pegar quantas amostras foram tiradas durante experimento. Máixma 600 amostra = 10 minutos de experimento
        cmdGET_RESULTS,     // Comando para ler as amostras tiradas durante experimento de todos os termômetros. Máixma 600 amostra = 10 minutos de experimento
        cmdEXP_START,       // Comando para dar início ao experimento
        cmdEXP_STOP,        // Comando para parar o experimento
        cmdGET_TEMP,        // Comando para ler as temperaturas
        cmdRST_STAT_TEMP,   // Comando para iniciar a estatisticas de temperatura
        cmdSET_TEMP_TYPE    // Comando para ajustar a grandeza da temperatura
                                // 0 - Ajuste via jumper
                                // 1 - Celcius
                                // 2 - Fahrenheit
    );

    tSensor = record
        stsCom:integer;	// Status de comunicação:
        				//		0: Sem comunicação com o dispositivo. O mesmo não está conectado, ou está desligado, ou não há dispositivo neste endereço.
		        		//		1: O multímetro recebeu uma função que não foi implementada;
				        //		2: Foi acessado a um endereço de registrador inexistente;
        				//		3: Foi tentado gravar um valor inválido no registrador do multímetro;
		        		//		4: Um irrecuperável erro ocorreu enquanto o multímetro estava tentando executar a ação solicitada;
				        //		5: Comunicação estabelecida com sucesso

        sts:integer;            // Status do sensor de temperatura
                                //      0: Sinaliza que o WorkTemp está lendo pela primeira vez a temperatura.
                                //          Isto somente acontece no momento que o WorkTemp é ligado.
                                //      1: O WorkTemp já contém valor da temperatura;
                                //      2: Sinaliza um erro, indica que a temperatura está abaixo da escala
                                //          permitida pelo WorkTemp. Ou seja, a conexão com o sensor
                                //          está em curto, seja o sensor em si ou o fio que se liga a ele;
                                //      3: Sinaliza um erro, indica que a temperatura está acima da escala
                                //          permitida pelo WorkTemp. Ou seja, a conexão com o sensor está aberta,
                                //          ou o sensor está danificado, ou o fio do sensor está partido
                                //          ou não está conectado ao WorkTemp.
        TemperatureType: integer;   //  Sinaliza se a grandeza da temperatura:
                                    //      0: Graus Celsius;
                                    //      1: Graus Fahrenheit.
        value:integer;      // valor da temperatura * 10
        valueMin:integer;   // valor da temperatura * 10
        valueMax:integer;   // valor da temperatura * 10
    end;

    tTerminal = class // (tList)
    private
        flog:textfile;
        FRegs: array [0..120] of word;              // registradores para comunicação via modbus
        FBaudRate: integer;
        FConnected: boolean;
        FSaveLog:boolean;                           // sinaliza para salvar ou não arquivo de LOG
        FTimeout: integer;
        FModBus:tModBus;
        Fid: integer;                               // Endereço do WorkTemo Slave a ser acessado
        FUart:tUart;

        procedure SetSaveLog(sl:boolean);
        procedure SetTimeout(tout:integer);
        procedure SetBaudRate(br:integer);
    public
        ErrorCom:integer;       // Salva o erro mais recente de comunicação via modbus
        Busy: boolean;          // Sinalia quando estamos eviando ou esperando resposta do WorkTemp
        Fimware:string;         // Versão do WorkTemp
        Model:string;           // Modelo do WorkTemp

        Action: integer;        // Sinaliza que ação o recurso de hardware está fazendo agora
                                    // 0: O recurso de hardware não está fazendo nada;
                                    // 1: O recurso de hardware está fazendo um experimento ou uma configuração nos termômetros.
        Status: integer;        // Guarda o status do experimento
                                    // 0: Sinaliza que o experimento ou processo de configuração dos termômetros foi feito com sucesso;
                                    // 1: Não foi possível se comunicar pelo menos com um termômetro;
                                    // 2: Pelo menos um dos termômetros deu erro de leitura de temperatura, ou a mesma ficou fora dos limites de trabalho
        nSamples:integer;       // Quantidades de amostras de todos os termômetros tiradas durante o experimento

        ThermAccessAddr: integer;  // Endereço do termometro a ser lido
        nThermAccessSamples: integer; // Quantidade de valores devemos ler no acesso. Valor máximo de 120
        ThermBufferSamples: array[0..nBUFFER_SAMPLES_READ-1] of integer;   // Buffer temporário da leitura dos registradores das asmostras de temperatura lida do RH

        Temperature: array[0..nTHERMOMETER-1] of tSensor;   // Dados dos sensors de temperatura do WorkTemp
        SetTempType: integer;

        property Timeout:integer read FTimeout write SetTimeout;
        property BaudRate:integer read FBaudRate write SetBaudRate;
        property Connected: boolean read FConnected write FConnected;
        property SaveLog:boolean read FSaveLog write SetSaveLog;

        constructor Create;
        destructor Destroy; override;
        function GetPorts(PortsList:TStrings):integer;
        function OpenConnection(PortCOM: string; id:integer):integer;
        procedure CloseConnection;
        function SendCommand(cmd: tCommand):integer;
    end;

implementation

// #################################################################################################
// SYSTEM
// #################################################################################################
constructor tTerminal.Create;
var x:integer;
begin
    FUart:= tUART.Create;
    FModBus:= tModBus.Create(FUart.Write, FUart.GetC, FUart.Purge);

    Busy := FALSE;
    BaudRate:= 7; // 115200 bps;
    ErrorCom := 0;

    for x:= 0 to nTHERMOMETER-1 do Temperature[x].stsCom := 0;
    ThermAccessAddr := $1000;
    nThermAccessSamples := 120;

    Fimware:= '';
    Model:= '';
end;

destructor tTerminal.Destroy;
begin
    FModBus.Free;

    if (Connected) then FUart.Close;
    FUart.Free;

    if FSaveLog then closefile(fLog);

    // CHAMA A FUNÇÃO DESTROY PAI
    inherited; // Destroi esta lista (tList)
end;

procedure tTerminal.SetTimeout(tout:integer);
begin
    if (tout < 1) or (tout>10) then tout:= 3;
    FTimeout := tout;
    FModBus.Timeout := tout;
end;

procedure tTerminal.SetSaveLog(sl:boolean);
var flogName:string;
begin
    if (sl) then begin
        flogName:= 'commands.log';
        assignfile(flog, flogName);
        Rewrite(flog);
        writeLn(flog, ' >>>>>>>>> INICIAÇÃO DO LOG DE COMANDOS - Data: ' + DateToStr(date) + ' Hora:' + TimeToStr(time) + ' <<<<<<<< ');
    end;
    FSaveLog := sl;
    FModBus.SaveLog := sl;
end;

procedure tTerminal.SetBaudRate(br:integer);
begin
    FBaudRate:= br;
    FUart.SetBaud(br);
end;


// #################################################################################################
// CONEXÃO
// #################################################################################################
function tTerminal.GetPorts(PortsList:TStrings):integer;
begin
     result:= uart_GetCommList(PortsList);
end;

// Abre uma conexão com o WorkTemp
// se a conexão foi aberta a função retorna 1 e com as vars Model e Version preenchidas
// senão retorna o código de erro modbus, também salvo em ErrorCom, caso não abriu a conexão
function tTerminal.OpenConnection(PortCOM: string; id:integer):integer;
begin
    Fid := id;
    if (Connected) then FUart.Close;

    if FSaveLog then writeLn(flog, format('Abrindo porta %s',[PortCOM]));

    // abrir porta UART
    if (not FUart.Open(PortCOM)) then begin
        ErrorCom := errMODBUS_OPEN_UART;
        exit(errMODBUS_OPEN_UART);      // Não prossegue se a porta não existe ou se está sendo usada
                                        //   por outro software
    end;

    // enviar um comando de solicitação de reconhecimento, neste caso vamos solicitar o modelo do WorkTemp
    result := SendCommand(cmdGET_INFO); // envia o comando ao WorkTemp

    if (result > 0 ) then begin
        result:= 1;
        Connected := TRUE;
    end else begin
        Connected := FALSE;
    end;
end;

procedure tTerminal.CloseConnection;
var x:integer;
begin
    if (not Connected) then exit;

    ErrorCom := 0;
    Busy := FALSE;
    FUart.Close;
    Connected:= FALSE;
    Fimware:= '';
    Model:= '';

    for x:= 0 to nTHERMOMETER-1 do Temperature[x].stsCom := 0;
    ThermAccessAddr := $1000;
    nThermAccessSamples := 120;
end;

// Envia um comando de leitura ou escrita nos registradores do WorkTemp.
// Para comando de leitura as respectivas variáveis no WorkSet serão atualizadas
// Para comando de escrita as respectivas variáveis no WorkSet devem estar preenchidas
// Retorna 1 se tudo ocorreu bem
// senão retorna o código de erro modbus, também salvo em ErrorCom
// ATENÇÃO: Para vars que consideram sinais é preciso usar o cast smallint, como as vars de temperatura
type tcmd = (readREGS, writeREG, writeREGS);
function tTerminal.SendCommand(cmd: tCommand):integer;
var addrInit, nRegs, x:integer;
    typeCMD: tcmd;
    value: word;
begin
    if (Busy) then exit(errMODBUS_BUSY);
    Busy := TRUE;

    // APONTA QUAIS REGISTRADORES A ACESSAR NO WORKTEMP
    // ---------------------------------------------------------------------------------------------

    // Comando para ler os registradores: modelo, versão firmware e modo de operações dos reles
    typeCMD := readREGS;
    addrInit := 0;
    nRegs:= 3;
    value := 0;
    case cmd of

    // Comando para ler que ação o recurso de hardware está fazendo agora
    cmdGET_ACTION: begin
        addrInit := $11;
        nRegs:= 1;
    end;

    // Comando para o status do experimento
    cmdGET_STATUS: begin
        addrInit := $12;
        nRegs:= 1;
    end;

    // Comando para pegar quantas amostras foram tiradas durante experimento. Máixma 600 amostra = 10 minutos de experimento
    cmdGET_SAMPLES: begin
        addrInit := $13;
        nRegs:= 1;
    end;

    // Comando para ajustar a grandeza da temperatura
    cmdSET_TEMP_TYPE: begin
        typeCMD := writeREG;
        addrInit := $14;
        nRegs:= 1;
        value := SetTempType;
    end;

    // Comando para dar início ao experimento
    cmdEXP_START: begin
        typeCMD := writeREG;
        addrInit := $15;
        nRegs:= 1;
    end;

    // Comando para parar o experimento
    cmdEXP_STOP: begin
        typeCMD := writeREG;
        addrInit := $16;
        nRegs:= 1;
    end;

    // Comando para iniciar a estatisticas de temperatura
    cmdRST_STAT_TEMP: begin
        typeCMD := writeREG;
        addrInit := $A001;
        nRegs:= 1;
    end;

    // Comando para ler as temperaturas
    cmdGET_TEMP: begin;
        addrInit := $400;
        nRegs:= nTHERMOMETER*5; // incluindo regs de temps min e max
    end;

    // Comando para ler as amostras tiradas durante experimento de todos os termômetros.
    cmdGET_RESULTS: begin
        addrInit := ThermAccessAddr;
        if nThermAccessSamples >= nBUFFER_SAMPLES_READ then nThermAccessSamples := nBUFFER_SAMPLES_READ;
        nRegs := nThermAccessSamples;
        for x := 0 to nBUFFER_SAMPLES_READ-1 do ThermBufferSamples[x] := 0;
    end;

    end;

    // ENVIA O COMANDO AO WORKTEMP
    // ---------------------------------------------------------------------------------------------
    if typeCMD = writeREG then begin
        if FSaveLog then writeLn(flog, format('Enviando comando de escrita REG addr 0x%x = valor',[addrInit, value]));
        result:= FModbus.Write_Register(Fid, addrInit, value);
    end else if typeCMD = writeREGS then begin
        if FSaveLog then writeLn(flog, format('Enviando comando de escrita REGS addr 0x%x len %d',[addrInit, nRegs]));
        result:= FModbus.Write_Registers(Fid, addrInit, nRegs, @FRegs);
    end else begin
        if FSaveLog then writeLn(flog, format('Enviando comando de leitura REGS addr 0x%x len %d',[addrInit, nRegs]));
        result:= FModbus.Read_Registers(Fid, addrInit, nRegs, @FRegs);
    end;

    if (result < 0) then begin
        if FSaveLog then writeLn(flog, format('    Erro %d',[result]));
        ErrorCom := result;
        Busy := FALSE;
        exit;
    end;
    ErrorCom := 0;

    if FSaveLog then begin
        writeLn(flog, format('    RX %d regs',[nRegs]));
        for x := 0 to nRegs-1 do begin
            writeLn(flog, format('        0x%x',[FRegs[x]]));
        end;
    end;


    // ATUALIZA VAR COM A LEITURA
    // ---------------------------------------------------------------------------------------------

    case cmd of

    // Comando para ler os registradores: modelo, versão firmware e modo de operações dos reles
    cmdGET_INFO: begin;
        Model := chr(FRegs[0] and $ff) + chr((FRegs[0] shr 8)) + chr(FRegs[1] and $ff) + chr((FRegs[1] shr 8));
        Fimware := chr(FRegs[2] and $ff) + '.' + chr((FRegs[2] shr 8));
    end;

    // Comando para ler que ação o recurso de hardware está fazendo agora
    cmdGET_ACTION: begin
        Action := FRegs[0];
    end;

    // Comando para o status do experimento
    cmdGET_STATUS: begin
        Status := FRegs[0];
    end;

    // Comando para pegar quantas amostras foram tiradas durante experimento
    cmdGET_SAMPLES: begin
        nSamples := FRegs[0];
    end;

    // Comando para ler as temperaturas no WorkTemp e os estados dos reles
    cmdGET_TEMP: begin
        for x:= 0 to nTHERMOMETER-1 do begin
   			Temperature[x].stsCom := FRegs[5*x] and $ff;
			Temperature[x].TemperatureType := (FRegs[(5*x)+1] and $10) shr 4;
			Temperature[x].sts := FRegs[(5*x)+1] and $f;
			Temperature[x].value := smallint((FRegs[(5*x)+2]));
            Temperature[x].valueMin := smallint((FRegs[(5*x)+3]));
            Temperature[x].valueMax := smallint((FRegs[(5*x)+4]));
        end;
    end;

    // Comando para ler as amostras tiradas durante experimento de todos os termômetros.
    cmdGET_RESULTS: begin
        for x:= 0 to nThermAccessSamples-1 do begin
             ThermBufferSamples[x] := smallint(FRegs[x]);
        end;
    end;
    end;

    Busy := FALSE;
end;

end.
